Submission.php

<?php

namespace Phad;

trait Submission {


    /** The sql row id from the last INSERT executed through Submission */
    public $lastInsertId;

    /** a pattern such as `/blog/{slug}/{id}/` to redirect to when a form completes submission. 
     * Typically the target is loaded from the html node like `<form target="/blog/{slug}/{id}/>`
     */
    public $target_url;

    /**
     * Set true to allow extra fields in the submission that are not defined in the form.
     */
    public bool $allow_extra_fields = false;

    /**
     * @issue(jan 31, 2022) this function uses `header()` & then `exit`s (unless you set `$phad->exit_on_redirect = false`) ... it works for now, but it might end up bein g a problem
     */
    public function redirect($ItemInfo, array $ItemRow){
        $target = $this->getSubmitTarget($ItemInfo, $ItemRow);
        if ($target===null)return;

        header("Location: {$target}", true);
        if ($this->exit_on_redirect){
            exit;
        }
    }

    /**
     *
     * @override for submission-handling
     * @return true/false for "allow submit"/"don't allow submit"
     */
    public function onSubmit($ItemInfo, &$ItemRow){
        return true;
    }
    /**
     * Called after an item is successfully submitted
     *
     * @override to setup a post-submit handler
     */
    public function onDidSubmit($ItemInfo, $ItemRow){}

    /**
     * @return true to proceed with deletion or false to stop
     * @override for submission-handling
     */
    public function onWillDelete($ItemInfo){
        return true;
    }

    public function did_delete($ItemInfo){
        $id = $ItemInfo->args['id'];
        if (!isset($ItemInfo->diddelete)||is_bool($ItemInfo->diddelete)){
            echo "\nSuccessfully deleted ".$ItemInfo->name." with id '$id'";
            return;
        }

        $functions = $this->parse_functions($ItemInfo->diddelete);
        foreach ($functions as $fn=> /* string */ $args){
            if ($fn=='print'){
                echo $args;
            } else if ($fn=='goto'){
                //@todo allow paramaterized goto, maybe???
                header("Cache-Control: no-cache");
                header("Location: ".$args);
                if ($this->exit_on_redirect){
                    exit;
                }
            } else if ($fn=='call'){
                $this->call($args, $ItemInfo);
            }

        }
    }

    public function delete($ItemInfo){
        if (!$this->onWillDelete($ItemInfo)){
            if (count($errors)==0){
                $ItemInfo->submit_errors[] = ['msg'=>"Deletion failed. Reason unkown. 'onDelete' error."];
            }
            return false;
        }

        $errors = &$ItemInfo->submit_errors;

        $errors = [];

        $table = $ItemInfo->table ?? strtolower($ItemInfo->name);
        $check_stmt = $this->pdo->prepare($sql="SELECT * from $table WHERE `id` = :id LIMIT 2");
        $check_stmt->execute(['id'=>$ItemInfo->args['id']]);
        $rows = $check_stmt->fetchAll(\PDO::FETCH_ASSOC);

        if (count($rows)==0){
            $msg = "Cannot delete '".$ItemInfo->name."'. None exist with id '".$ItemInfo->args['id']."'";
            $errors[] = ['msg'=>$msg];
            echo $msg;
            return false;
        } else if (count($rows)>1){
            $msg = "Cannot delete '".$ItemInfo->name."'. Multiple exist with id '".$ItemInfo->args['id']."'. Database Error.";
            $errors[] = ['msg'=>$msg];
            echo $msg;
            return false;
        }

        $submitter = new \Phad\PDOSubmitter($this->pdo);
        try {
            $didSubmit = $submitter->delete($table, $ItemInfo->args['id']);
        } catch (\PDOException $pe){
            $errors[] = ['msg'=>"Deletion was allowed, but there was an unknown technical error. Error code '".$this->pdo->errorCode()."'"];
            echo "Deletion was allowed, but there was an unknown technical error. Error code '".$this->pdo->errorCode()."'";
            return false;

        }
        if (!$didSubmit){

            $errors[] = ['msg'=>"Deletion was allowed, but there was an unknown technical error. Error code '".$this->pdo->errorCode()."'"];
            echo "Deletion was allowed, but there was an unknown technical error. Error code '".$this->pdo->errorCode()."'";
            return false;

//             $query = new \Phad\Query();
//             $ii = (array)$ItemInfo;
//             unset($ii['args']);
//             var_dump($ii);
//             exit;
//             // $args = $ItemInfo->args;
//             // unset($args['phad']);
//             // var_dump($args);
//             // exit;
//             $items = $query->get($ItemInfo->name, ['type'=>'default'], $ItemInfo->args, $ItemInfo->type, null);
//
//             $item = $this->
// // $query_info
//
//             $errors[] = ['msg'=>"Deletion was allowed, but there was an unknown technical error. Error code '".$this->pdo->errorCode()."'"];
//             echo "Deletion was allowed, but there was an unknown technical error. Error code '".$this->pdo->errorCode()."'";
//             return false;
        }

        return true;
    }


    /**
     * Convert the array of failed columsn returned by Form Validator's validate() method into a nice array of [ [ 'msg'=>'erro rmessages'],['msg'=>'another error'] ]
     */
    public function make_error_messages(array $errors): array{
        $msgs = [];
        foreach ($errors as $prop=>$e){
            if ($e['failed_value']==''&&$e['required']){
                $msgs[]['msg'] = "'$prop' is required";
            } 
            //@TODO handle other error cases
        }

        return $msgs;
    }

    public function check_post_validity($ItemInfo, array &$ItemRow){
    
        $errors = &$ItemInfo->submit_errors;

        $validator = new \Phad\FormValidator($ItemInfo->properties, $errors);
        $validator->allow_extra_fields = &$this->allow_extra_fields;
        $validator->validators = &$this->validators;

        $errors = [];

        if ($validator->validate($ItemRow, $errors, $failed_columns)===false){

            if (count($errors)==0){
                $errors[] = ['msg'=>"Validation failed. Reason unkown. 'validation' error"];
            }
            return false;
        }
        return true;
    }
    /**
     * If the submit is an `INSERT`, then `$ItemRow` will have its `id` set
     *
     * @param &$ItemInfo the item data object
     * @param &$ItemRow key=>value array.
     */
    public function submit($ItemInfo, array &$ItemRow): bool {

        $errors = &$ItemInfo->submit_errors;

        if (!$this->onSubmit($ItemInfo, $ItemRow)){
            if (count($errors)==0){
                $ItemInfo->submit_errors[] = ['msg'=>"Validation failed. Reason unkown. 'onSubmit' error."];
            }
            return false;
        }


        if (!$this->check_post_validity($ItemInfo, $ItemRow)){
            return false;
        }

        $submitter = new \Phad\PDOSubmitter($this->pdo);
        try {
            // echo 'will try submit';
            $didSubmit = $submitter->submit($ItemInfo->table ?? strtolower($ItemInfo->name), $ItemRow);
            if ($didSubmit)$this->onDidSubmit($ItemInfo, $ItemRow);
            // print_r($ItemRow);
            // exit;
        } catch (\PDOException $pe){
            if (isset($this->router)&&$this->router->lia->debug){
                $errors[] = ['msg'=>"Submission was valid, but there was an unknown technical error. Error '".$pe->getMessage()."'"];
            } else {
                $errors[] = ['msg'=>"Submission was valid, but there was an unknown technical error. Error code '".$this->pdo->errorCode()."'"];
            }
            error_log($pe.'');
            return false;
        }
        if (!$didSubmit){
            if (isset($this->router)&&$this->router->lia->debug){
                $errors[] = ['msg'=>"Submission was valid, but there was an unknown technical error. Error '".$pe->getMessage()."'"];
            } else {
                $errors[] = ['msg'=>"Submission was valid, but there was an unknown technical error. Error code '".$this->pdo->errorCode()."'"];
            }
            return false;
        }
        $this->lastInsertId = $submitter->lastInsertId();
        if (is_numeric($this->lastInsertId)){
            $ItemRow['id'] = $ItemRow['id'] ?? $this->lastInsertId;
        }
        return true;

    }


    /**
     *
     * @issue(jan 31, 2022) this method is stupid & should only take in what it needs at minimum (target + args, probably)
     */
    protected function getSubmitTarget($ItemInfo, $ItemRow){
        $target = $ItemInfo->target 
            ?? $this->target_url
            // ?? '/';
            ?? null;
        if ($target==null)return null;
        $target = $this->fillDynamicTarget($target, $ItemInfo, $ItemRow);
        $target = $this->normalizeTarget($target);
        return $target;
    }



    /**
     *
     * @issue(jan 31, 2022) this method is stupid & should only take in what it needs at minimum (target + args, probably) ... and WHY am i getting object from row? instead of just using the item row ... ohhhh ... say, the object has a magical `slug` property, but the ROW does not store a slug .... that makes objectFromRow necessary ... which makes this all a bit less stupid.
     * @issue(jan 31, 2022) this depends upon liaison's router & i don't like that ... i'd rather use handlers & then temporarily use liaison's router that way ... but idk ... it's okay for now
     */
    protected function fillDynamicTarget($targetUrl, $ItemInfo, $ItemRow){
        $url = parse_url($targetUrl);
        $router = $this->router;
        $parsed = $router->decode_pattern($url['path']);
        if (count($parsed['params'])==0)return $targetUrl;
        $ItemObj = $this->object_from_row($ItemRow, $ItemInfo);
        $fillWith = [];
        foreach ($parsed['params'] as $i=>$prop){
            $fillWith[$prop] = $ItemObj->$prop;
        }
        $newPath = $router->decoded_pattern_to_url($parsed, $fillWith);

        return $newPath;
    }

    /**
     * For urls containing protocol://domain.tld, just return the target
     * else add the current protocol://domain.tld
     */
    protected function normalizeTarget($targetUrl){
        $url = parse_url($targetUrl);
        if (isset($url['host'])&&isset($url['scheme'])){
            return $targetUrl;
        }
        if (substr($targetUrl,0,1)!='/')$targetUrl  = '/'.$targetUrl;
        $host = $_SERVER['HTTP_HOST'];
        $protocol = empty($_SERVER['HTTPS']) ? 'http://' : 'https://';

        $targetUrl = str_replace('//','/', $targetUrl);
        return $protocol.$host.$targetUrl;
    }


    /**
     * @example `foreach ($phad->getHeaders() as $header){header(...$header);}`
     * @return array of headers.
     */
    // public function getHeaders(): array{
    //     return $this->headers;
    // }
}